home *** CD-ROM | disk | FTP | other *** search
/ SuperHack / SuperHack CD.bin / CODING / DELPHI / COMM32.ZIP / COMM32.PAS next >
Encoding:
Pascal/Delphi Source File  |  1996-03-19  |  44.7 KB  |  1,535 lines

  1. unit Comm32;
  2. //
  3. // This Communications Component is implemented using separate Read and Write
  4. // threads. Messages from the threads are posted to the Comm control which is
  5. // an invisible window. To handle data from the comm port, simply
  6. // attach a handler to 'OnReceiveData'. There is no need to free the memory
  7. // buffer passed to this handler. If TAPI is used to open the comm port, some
  8. // changes to this component are needed ('StartComm' currently opens the comm
  9. // port). The 'OnRequestHangup' event is included to assist this.
  10. //
  11. // David Wann
  12. // Stamina Software
  13. // 28/02/96
  14. // davidwann@hunterlink.net.au
  15.  
  16. interface
  17.  
  18. uses
  19.     Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  20.     Misc;
  21.  
  22. const
  23.     // messages from read/write threads
  24.     PWM_GOTCOMMDATA = WM_USER + 1;
  25.     PWM_REQUESTHANGUP = WM_USER + 2;
  26.  
  27. type
  28.     ECommsError = class( Exception );
  29.  
  30.     TReadThread = class( TThread )
  31.     protected
  32.         procedure Execute; override;
  33.     public
  34.         hCommFile:             THandle;
  35.         hCloseEvent:        THandle;
  36.         hComm32Window:        THandle;
  37.         function SetupCommEvent( lpOverlappedCommEvent: POverlapped;
  38.                         var lpfdwEvtMask: DWORD ): Boolean;
  39.         function SetupReadEvent( lpOverlappedRead: POverlapped;
  40.                         lpszInputBuffer: LPSTR; dwSizeofBuffer: DWORD;
  41.                         var lpnNumberOfBytesRead: DWORD ): Boolean;
  42.         function HandleCommEvent( lpOverlappedCommEvent: POverlapped;
  43.                         var lpfdwEvtMask: DWORD; fRetrieveEvent: Boolean ): Boolean;
  44.         function HandleReadEvent( lpOverlappedRead: POverlapped;
  45.                         lpszInputBuffer: LPSTR; dwSizeofBuffer: DWORD;
  46.                         var lpnNumberOfBytesRead: DWORD ): Boolean;
  47.         function HandleReadData( lpszInputBuffer: LPCSTR; dwSizeofBuffer: DWORD ): Boolean;
  48.         function ReceiveData( lpNewString: LPSTR; dwSizeofNewString: DWORD ): BOOL;
  49.         procedure PostHangupCall;
  50.     end;
  51.  
  52.     TWriteThread = class( TThread )
  53.     protected
  54.         procedure Execute; override;
  55.         function HandleWriteData( lpOverlappedWrite: POverlapped;
  56.                 pDataToWrite: PChar; dwNumberOfBytesToWrite: DWORD): Boolean;
  57.     public
  58.         hCommFile:             THandle;
  59.         hCloseEvent:        THandle;
  60.         hComm32Window:        THandle;
  61.         function WriteComm( pDataToWrite: LPCSTR; dwSizeofDataToWrite: DWORD ): Boolean;
  62.         procedure PostHangupCall;
  63.     end;
  64.  
  65.     TReceiveDataEvent = procedure( Buffer: Pointer; BufferLength: Word ) of object;
  66.  
  67.     TComm32 = class( TComponent )
  68.     private
  69.         { Private declarations }
  70.         ReadThread:                TReadThread;
  71.         WriteThread:            TWriteThread;
  72.         FCommsLogFileName,
  73.         FCommPort:                string;
  74.         hCommFile:                 THandle;
  75.         hCloseEvent:            THandle;
  76.         FOnReceiveData:         TReceiveDataEvent;
  77.         FOnRequestHangup:        TNotifyEvent;
  78.         FHWnd:                    THandle;
  79.         FBaudRate:            DWORD;
  80.  
  81.         procedure SetCommsLogFileName( LogFileName: string );
  82.         function GetReceiveDataEvent: TReceiveDataEvent;
  83.         procedure SetReceiveDataEvent( AReceiveDataEvent: TReceiveDataEvent );
  84.         function GetRequestHangupEvent: TNotifyEvent;
  85.         procedure SetRequestHangupEvent( ARequestHangupEvent: TNotifyEvent );
  86.         procedure CommWndProc( var msg: TMessage );
  87.     protected
  88.         { Protected declarations }
  89.         procedure CloseReadThread;
  90.         procedure CloseWriteThread;
  91.         procedure ReceiveData( Buffer: PChar; BufferLength: Word );
  92.         procedure RequestHangup;
  93.     public
  94.         { Public declarations }
  95.         constructor Create( AOwner: TComponent ); override;
  96.         destructor Destroy; override;
  97.         function StartComm: Boolean;
  98.         procedure StopComm;
  99.         function WriteCommData( pDataToWrite: PChar; dwSizeofDataToWrite: Word ): Boolean;
  100.     published
  101.         { Published declarations }
  102.         property BaudRate: DWORD read FBaudRate write FBaudRate;
  103.         property CommPort: string read FCommPort write FCommPort;
  104.         property CommsLogFileName: string read FCommsLogFileName write SetCommsLogFileName;
  105.         property OnReceiveData: TReceiveDataEvent
  106.                 read GetReceiveDataEvent write SetReceiveDataEvent;
  107.         property OnRequestHangup: TNotifyEvent
  108.                 read GetRequestHangupEvent write SetRequestHangupEvent;
  109.     end;
  110.  
  111. const
  112. // This is the message posted to the WriteThread
  113. // When we have something to write.
  114.     PWM_COMMWRITE = WM_USER+1;
  115.  
  116. // Default size of the Input Buffer used by this code.
  117.     INPUTBUFFERSIZE = 2048;
  118.  
  119. var
  120.     CommsLogFile:    Text; // means you can only debug 1 component at a time
  121.  
  122.  
  123. procedure LogDebugInfo( outstr: PChar );
  124. procedure LogDebugLastError( dwLastError: DWORD; szPrefix: LPSTR );
  125. procedure Register;
  126.  
  127. implementation
  128.  
  129. var
  130.     CommsLogName:    string; // used as a check if file is assigned
  131.  
  132. (******************************************************************************)
  133. //                                    TCOMM32 PUBLIC METHODS
  134. (******************************************************************************)
  135.  
  136. constructor TComm32.Create( AOwner: TComponent );
  137. begin
  138.     inherited Create( AOwner );
  139.     FCommPort := 'COM2';
  140.     FCommsLogFileName := '';
  141.     CommsLogName := '';
  142.     ReadThread := nil;
  143.     WriteThread := nil;
  144.     hCommFile := 0;
  145.     if not (csDesigning in ComponentState) then
  146.         FHWnd := AllocateHWnd(CommWndProc);
  147. end;
  148.  
  149. destructor TComm32.Destroy;
  150. begin
  151.     if not (csDesigning in ComponentState) then
  152.     begin
  153.         DeallocateHWnd(FHwnd);
  154.     end;
  155.     inherited Destroy;
  156. end;
  157.  
  158. //
  159. //  FUNCTION: StartComm
  160. //
  161. //  PURPOSE: Starts communications over the comm port.
  162. //
  163. //  PARAMETERS:
  164. //    hNewCommFile - This is the COMM File handle to communicate with.
  165. //                   This handle is obtained from TAPI.
  166. //
  167. //  RETURN VALUE:
  168. //    TRUE if able to setup the communications.
  169. //
  170. //  COMMENTS:
  171. //
  172. //    StartComm makes sure there isn't communication in progress already,
  173. //    creates a Comm file, and creates the read and write threads.  It
  174. //    also configures the hNewCommFile for the appropriate COMM settings.
  175. //
  176. //    If StartComm fails for any reason, it's up to the calling application
  177. //    to close the Comm file handle.
  178. //
  179. //
  180. function TComm32.StartComm: Boolean;
  181. var
  182.     commtimeouts:    TCommTimeouts;
  183.     dcb:                Tdcb;
  184.     commprop:        TCommProp;
  185.     fdwEvtMask:        DWORD;
  186.     hNewCommFile: THandle;
  187. begin
  188.     // Are we already doing comm?
  189.     if (hCommFile <> 0) then
  190.         raise ECommsError.Create( 'Already have a comm file open' );
  191.  
  192.     if CommsLogFileName <> '' then
  193.     begin
  194.         AssignFile( CommsLogFile, fCommsLogFileName );
  195.         Rewrite( CommsLogFile );
  196.     end;
  197.  
  198.     hNewCommFile := CreateFile(
  199.                             PChar(fCommPort),
  200.                             GENERIC_READ+GENERIC_WRITE,
  201.                             0, {not shared}
  202.                             nil, {no security ??}
  203.                             OPEN_EXISTING,
  204.                             {FILE_ATTRIBUTE_NORMAL+}FILE_FLAG_OVERLAPPED,
  205.                             0 {template} );
  206.     if hNewCommFile = INVALID_HANDLE_VALUE then
  207.         raise ECommsError.Create( 'Error opening com port' );
  208.  
  209.     // Is this a valid comm handle?
  210.     if GetFileType( hNewCommFile ) <> FILE_TYPE_CHAR then
  211.         raise ECommsError.Create( 'File handle is not a comm handle. ' );
  212.  
  213.     // Its ok to continue.
  214.  
  215.     hCommFile := hNewCommFile;
  216.  
  217.     // Setting and querying the comm port configurations.
  218.  
  219.     // Configure the comm settings.
  220.     // NOTE: Most Comm settings can be set through TAPI, but this means that
  221.     //       the CommFile will have to be passed to this component.
  222.  
  223.     GetCommState( hNewCommFile, dcb );
  224.     GetCommProperties( hNewCommFile, commprop );
  225.     GetCommMask( hCommFile, fdwEvtMask );
  226.     GetCommTimeouts( hCommFile, commtimeouts );
  227.  
  228.     // The CommTimeout numbers will very likely change if you are
  229.     // coding to meet some kind of specification where
  230.     // you need to reply within a certain amount of time after
  231.     // recieving the last byte.  However,  If 1/4th of a second
  232.     // goes by between recieving two characters, its a good
  233.     // indication that the transmitting end has finished, even
  234.     // assuming a 1200 baud modem.
  235.  
  236.     commtimeouts.ReadIntervalTimeout         := 250;
  237.     commtimeouts.ReadTotalTimeoutMultiplier  := 0;
  238.     commtimeouts.ReadTotalTimeoutConstant    := 0;
  239.     commtimeouts.WriteTotalTimeoutMultiplier := 0;
  240.     commtimeouts.WriteTotalTimeoutConstant   := 0;
  241.  
  242.     SetCommTimeouts( hCommFile, commtimeouts );
  243.  
  244.     // fAbortOnError is the only DCB dependancy in TapiComm.
  245.     // Can't guarentee that the SP will set this to what we expect.
  246.     {dcb.fAbortOnError := False; NOT VALID}
  247.     dcb.BaudRate := FBaudRate;
  248.     SetCommState( hNewCommFile, dcb );
  249.  
  250.     // Create the event that will signal the threads to close.
  251.     hCloseEvent := CreateEvent( nil, True, False, nil );
  252.  
  253.     if hCloseEvent = 0 then
  254.     begin
  255.          LogDebugLastError( GetLastError, 'Unable to CreateEvent: ' );
  256.          hCommFile := 0;
  257.          Result := False;
  258.          Exit
  259.     end;
  260.  
  261.     // Create the Read thread.
  262.     try
  263.         ReadThread := TReadThread.Create( True {suspended} );
  264.     except
  265.         LogDebugLastError( GetLastError, 'Unable to create Read thread' );
  266.         raise ECommsError.Create( 'Unable to create Read thread' );
  267.     end;
  268.     ReadThread.hCommFile := hCommFile;
  269.     ReadThread.hCloseEvent := hCloseEvent;
  270.     ReadThread.hComm32Window := FHWnd;
  271.     ReadThread.Resume;
  272.  
  273.     // Comm threads should have a higher base priority than the UI thread.
  274.     // If they don't, then any temporary priority boost the UI thread gains
  275.     // could cause the COMM threads to loose data.
  276.     ReadThread.Priority := tpHighest;
  277.  
  278.     // Create the Write thread.
  279.     try
  280.         WriteThread := TWriteThread.Create( True {suspended} );
  281.     except
  282.         LogDebugLastError( GetLastError, 'Unable to create Write thread' );
  283.         raise ECommsError.Create( 'Unable to create Write thread' );
  284.     end;
  285.     WriteThread.hCommFile := hCommFile;
  286.     WriteThread.hCloseEvent := hCloseEvent;
  287.     WriteThread.hComm32Window := FHWnd;
  288.     WriteThread.Resume;
  289.  
  290.     ReadThread.Priority := tpHigher;
  291.  
  292.     // Everything was created ok.  Ready to go!
  293.     Result := True;
  294. end; {TComm32.StartComm}
  295.  
  296. //
  297. //  FUNCTION: StopComm
  298. //
  299. //  PURPOSE: Stop and end all communication threads.
  300. //
  301. //  PARAMETERS:
  302. //    none
  303. //
  304. //  RETURN VALUE:
  305. //    none
  306. //
  307. //  COMMENTS:
  308. //
  309. //    Tries to gracefully signal all communication threads to
  310. //    close, but terminates them if it has to.
  311. //
  312. //
  313. procedure TComm32.StopComm;
  314. begin
  315.     // No need to continue if we're not communicating.
  316.     if hCommFile = 0 then
  317.         Exit;
  318.  
  319.     LogDebugInfo( 'Stopping the Comm' );
  320.  
  321.      // Close the threads.
  322.     CloseReadThread;
  323.     CloseWriteThread;
  324.  
  325.     // Not needed anymore.
  326.     CloseHandle( hCloseEvent );
  327.  
  328.     // Now close the comm port handle.
  329.     CloseHandle( hCommFile );
  330.     hCommFile := 0;
  331.     if fCommsLogFileName <> '' then
  332.         CloseFile( CommsLogFile );
  333. end; {TComm32.StopComm}
  334.  
  335. //
  336. //  FUNCTION: WriteCommData(PChar, Word)
  337. //
  338. //  PURPOSE: Send a String to the Write Thread to be written to the Comm.
  339. //
  340. //  PARAMETERS:
  341. //    pszStringToWrite     - String to Write to Comm port.
  342. //    nSizeofStringToWrite - length of pszStringToWrite.
  343. //
  344. //  RETURN VALUE:
  345. //    Returns TRUE if the PostMessage is successful.
  346. //    Returns FALSE if PostMessage fails or Write thread doesn't exist.
  347. //
  348. //  COMMENTS:
  349. //
  350. //    This is a wrapper function so that other modules don't care that
  351. //    Comm writing is done via PostMessage to a Write thread.  Note that
  352. //    using PostMessage speeds up response to the UI (very little delay to
  353. //    'write' a string) and provides a natural buffer if the comm is slow
  354. //    (ie:  the messages just pile up in the message queue).
  355. //
  356. //    Note that it is assumed that pszStringToWrite is allocated with
  357. //    LocalAlloc, and that if WriteCommData succeeds, its the job of the
  358. //    Write thread to LocalFree it.  If WriteCommData fails, then its
  359. //    the job of the calling function to free the string.
  360. //
  361. //
  362. function TComm32.WriteCommData( pDataToWrite: PChar; dwSizeofDataToWrite: Word ): Boolean;
  363. var
  364.     Buffer:    Pointer;
  365. begin
  366.     if WriteThread <> nil then
  367.     begin
  368.         Buffer := Pointer(LocalAlloc( LPTR, dwSizeofDataToWrite+1 ));
  369.         Move( pDataToWrite^, Buffer^, dwSizeofDataToWrite );
  370.         if PostThreadMessage( WriteThread.ThreadID, PWM_COMMWRITE,
  371.                      WPARAM(dwSizeofDataToWrite), LPARAM(Buffer) ) then
  372.         begin
  373.             Result := true;
  374.             Exit;
  375.         end
  376.         else
  377.             LogDebugInfo( 'Failed to Post to Write thread. ' );
  378.     end
  379.     else
  380.         LogDebugInfo( 'Write thread not created' );
  381.  
  382.     Result := False;
  383. end; {TComm32.WriteCommData}
  384.  
  385. (******************************************************************************)
  386. //                                    TCOMM32 PROTECTED METHODS
  387. (******************************************************************************)
  388.  
  389. //
  390. //  FUNCTION: CloseReadThread
  391. //
  392. //  PURPOSE: Close the Read Thread.
  393. //
  394. //  PARAMETERS:
  395. //    none
  396. //
  397. //  RETURN VALUE:
  398. //    none
  399. //
  400. //  COMMENTS:
  401. //
  402. //    Closes the Read thread by signaling the CloseEvent.
  403. //    Purges any outstanding reads on the comm port.
  404. //
  405. //    Note that terminating a thread leaks memory.
  406. //    Besides the normal leak incurred, there is an event object
  407. //    that doesn't get closed.  This isn't worth worrying about
  408. //    since it shouldn't happen anyway.
  409. //
  410. //
  411. procedure TComm32.CloseReadThread;
  412. begin
  413.     // If it exists...
  414.     if ReadThread <> nil then
  415.     begin
  416.         LogDebugInfo( 'Closing Read Thread ');
  417.  
  418.         // Signal the event to close the worker threads.
  419.         SetEvent( hCloseEvent );
  420.  
  421.         // Purge all outstanding reads
  422.         PurgeComm( hCommFile, PURGE_RXABORT + PURGE_RXCLEAR );
  423.  
  424.         // Wait 10 seconds for it to exit.  Shouldn't happen.
  425.         if (WaitForSingleObject(ReadThread.Handle, 10000) = WAIT_TIMEOUT) then
  426.         begin
  427.             LogDebugInfo( 'Read thread not exiting.  Terminating it.' );
  428.             ReadThread.Terminate;
  429.         end;
  430.         ReadThread.Free;
  431.         ReadThread := nil;
  432.     end;
  433. end; {TComm32.CloseReadThread}
  434.  
  435.  
  436. //
  437. //  FUNCTION: CloseWriteThread
  438. //
  439. //  PURPOSE: Closes the Write Thread.
  440. //
  441. //  PARAMETERS:
  442. //    none
  443. //
  444. //  RETURN VALUE:
  445. //    none
  446. //
  447. //  COMMENTS:
  448. //
  449. //    Closes the write thread by signaling the CloseEvent.
  450. //    Purges any outstanding writes on the comm port.
  451. //
  452. //    Note that terminating a thread leaks memory.
  453. //    Besides the normal leak incurred, there is an event object
  454. //    that doesn't get closed.  This isn't worth worrying about
  455. //    since it shouldn't happen anyway.
  456. //
  457. //
  458. procedure TComm32.CloseWriteThread;
  459. begin
  460.     // If it exists...
  461.     if WriteThread <> nil then
  462.     begin
  463.         LogDebugInfo( 'Closing Write Thread' );
  464.  
  465.         // Signal the event to close the worker threads.
  466.         SetEvent(hCloseEvent);
  467.  
  468.         // Purge all outstanding writes.
  469.         PurgeComm(hCommFile, PURGE_TXABORT + PURGE_TXCLEAR);
  470.  
  471.         // Wait 10 seconds for it to exit.  Shouldn't happen.
  472.         if WaitForSingleObject( WriteThread.Handle, 10000 ) = WAIT_TIMEOUT then
  473.         begin
  474.             LogDebugInfo( 'Write thread not exiting.  Terminating it.' );
  475.             WriteThread.Terminate;
  476.         end;
  477.         WriteThread.Free;
  478.         WriteThread := nil;
  479.     end;
  480. end; {TComm32.CloseWriteThread}
  481.  
  482. procedure TComm32.ReceiveData( Buffer: PChar; BufferLength: Word );
  483. begin
  484.     if Assigned(FOnReceiveData) then
  485.         FOnReceiveData( Buffer, BufferLength );
  486. end;
  487.  
  488. procedure TComm32.RequestHangup;
  489. begin
  490.     if Assigned(FOnRequestHangup) then
  491.         FOnRequestHangup( Self );
  492. end;
  493.  
  494. (******************************************************************************)
  495. //                                    TCOMM32 PRIVATE METHODS
  496. (******************************************************************************)
  497.  
  498. procedure TComm32.SetCommsLogFileName( LogFileName: string );
  499. begin
  500.     CommsLogName := LogFileName;
  501.     FCommsLogFileName := LogFileName;
  502. end;
  503.  
  504. procedure TComm32.CommWndProc( var msg: TMessage );
  505. begin
  506.     case msg.msg of
  507.         PWM_GOTCOMMDATA:
  508.         begin
  509.             ReceiveData( PChar(msg.LParam), msg.WParam );
  510.             LocalFree( msg.LParam );
  511.         end;
  512.         PWM_REQUESTHANGUP:
  513.             RequestHangup;
  514.     end;
  515. end;
  516.  
  517. function TComm32.GetReceiveDataEvent: TReceiveDataEvent;
  518. begin
  519.     Result := FOnReceiveData;
  520. end;
  521.  
  522. procedure TComm32.SetReceiveDataEvent( AReceiveDataEvent: TReceiveDataEvent );
  523. begin
  524.     FOnReceiveData := AReceiveDataEvent;
  525. end;
  526.  
  527. function TComm32.GetRequestHangupEvent: TNotifyEvent;
  528. begin
  529.     Result := FOnRequestHangup;
  530. end;
  531.  
  532. procedure TComm32.SetRequestHangupEvent( ARequestHangupEvent: TNotifyEvent );
  533. begin
  534.     FOnRequestHangup := ARequestHangupEvent;
  535. end;
  536.  
  537.  
  538. (******************************************************************************)
  539. //                                            READ THREAD
  540. (******************************************************************************)
  541.  
  542. //
  543. //  PROCEDURE: TReadThread.Execute
  544. //
  545. //  PURPOSE: This is the starting point for the Read Thread.
  546. //
  547. //  PARAMETERS:
  548. //    None.
  549. //
  550. //  RETURN VALUE:
  551. //    None.
  552. //
  553. //  COMMENTS:
  554. //
  555. //    The Read Thread uses overlapped ReadFile and sends any data
  556. //    read from the comm port to the Comm32Window.  This is
  557. //    eventually done through a PostMessage so that the Read Thread
  558. //    is never away from the comm port very long.  This also provides
  559. //    natural desynchronization between the Read thread and the UI.
  560. //
  561. //    If the CloseEvent object is signaled, the Read Thread exits.
  562. //
  563. //      Separating the Read and Write threads is natural for a application
  564. //    where there is no need for synchronization between
  565. //    reading and writing.  However, if there is such a need (for example,
  566. //    most file transfer algorithms synchronize the reading and writing),
  567. //    then it would make a lot more sense to have a single thread to handle
  568. //    both reading and writing.
  569. //
  570. //
  571. procedure TReadThread.Execute;
  572. var
  573.      szInputBuffer:    array[0..INPUTBUFFERSIZE-1] of Char;
  574.      nNumberOfBytesRead:    DWORD;
  575.  
  576.      HandlesToWaitFor:    array[0..2] of THandle;
  577.      dwHandleSignaled:    DWORD;
  578.  
  579.      fdwEvtMask:            DWORD;
  580.  
  581.      // Needed for overlapped I/O (ReadFile)
  582.      overlappedRead:        TOverlapped;
  583.  
  584.      // Needed for overlapped Comm Event handling.
  585.      overlappedCommEvent:    TOverlapped;
  586. label
  587.     EndReadThread;
  588. begin
  589.  
  590.     FillChar( overlappedRead, Sizeof(overlappedRead), 0 );
  591.     FillChar( overlappedCommEvent, Sizeof(overlappedCommEvent), 0 );
  592.  
  593.     // Lets put an event in the Read overlapped structure.
  594.     overlappedRead.hEvent := CreateEvent( nil, True, True, nil);
  595.     if overlappedRead.hEvent = 0 then
  596.     begin
  597.          LogDebugLastError( GetLastError, 'Unable to CreateEvent: ' );
  598.          PostHangupCall;
  599.          goto EndReadThread;
  600.     end;
  601.  
  602.     // And an event for the CommEvent overlapped structure.
  603.     overlappedCommEvent.hEvent := CreateEvent( nil, True, True, nil);
  604.     if overlappedCommEvent.hEvent = 0 then
  605.     begin
  606.          LogDebugLastError( GetLastError, 'Unable to CreateEvent: ' );
  607.          PostHangupCall();
  608.          goto EndReadThread;
  609.     end;
  610.  
  611.     // We will be waiting on these objects.
  612.     HandlesToWaitFor[0] := hCloseEvent;
  613.     HandlesToWaitFor[1] := overlappedCommEvent.hEvent;
  614.     HandlesToWaitFor[2] := overlappedRead.hEvent;
  615.  
  616.  
  617.     // Setup CommEvent handling.
  618.  
  619.     // Set the comm mask so we receive error signals.
  620.     if not SetCommMask(hCommFile, EV_ERR) then
  621.     begin
  622.         LogDebugLastError( GetLastError, 'Unable to SetCommMask: ' );
  623.         PostHangupCall;
  624.         goto EndReadThread;
  625.     end;
  626.  
  627.     // Start waiting for CommEvents (Errors)
  628.     if not SetupCommEvent( @overlappedCommEvent,  fdwEvtMask ) then
  629.     begin
  630.         LogDebugLastError( GetLastError, 'Unable to SetupCommEvent1: ' );
  631.         PostHangupCall;
  632.         goto EndReadThread;
  633.     end;
  634.  
  635.     // Start waiting for Read events.
  636.     if not SetupReadEvent( @overlappedRead,
  637.                     szInputBuffer, INPUTBUFFERSIZE,
  638.                      nNumberOfBytesRead ) then
  639.     begin
  640.         LogDebugLastError( GetLastError, 'Unable to SetupReadEvent: ' );
  641.         PostHangupCall;
  642.         goto EndReadThread;
  643.     end;
  644.  
  645.     // Keep looping until we break out.
  646.     while True do
  647.     begin
  648.         // Wait until some event occurs (data to read; error; stopping).
  649.         dwHandleSignaled :=
  650.               WaitForMultipleObjects(3, @HandlesToWaitFor,
  651.                     False, INFINITE);
  652.  
  653.          // Which event occured?
  654.         case dwHandleSignaled of
  655.             WAIT_OBJECT_0:     // Signal to end the thread.
  656.             begin
  657.                 // Time to exit.
  658.                 OutputDebugString( 'Time to Exit' );
  659.                 goto EndReadThread;
  660.             end;
  661.  
  662.             WAIT_OBJECT_0 + 1: // CommEvent signaled.
  663.             begin
  664.                 // Handle the CommEvent.
  665.                 if not HandleCommEvent( @overlappedCommEvent,  fdwEvtMask, TRUE ) then
  666.                 begin
  667.                     PostHangupCall;
  668.                     LogDebugLastError( GetLastError, 'Unable HandleCommEvent: ' );
  669.                     goto EndReadThread;
  670.                 end;
  671.  
  672.                 // Start waiting for the next CommEvent.
  673.                 if not SetupCommEvent( @overlappedCommEvent,  fdwEvtMask ) then
  674.                 begin
  675.                     PostHangupCall;
  676.                     LogDebugLastError( GetLastError, 'Unable to SetupCommEvent2: ' );
  677.                     goto EndReadThread;
  678.                 end;
  679.                 {break;??}
  680.             end;
  681.  
  682.             WAIT_OBJECT_0 + 2: // Read Event signaled.
  683.             begin
  684.                 // Get the new data!
  685.                 if not HandleReadEvent( @overlappedRead,
  686.                                     szInputBuffer, INPUTBUFFERSIZE,
  687.                                      nNumberOfBytesRead ) then
  688.                 begin
  689.                     PostHangupCall;
  690.                     LogDebugLastError( GetLastError, 'Unable to HandleReadEvent: ' );
  691.                     goto EndReadThread;
  692.                 end;
  693.  
  694.                 // Wait for more new data.
  695.                 if not SetupReadEvent( @overlappedRead,
  696.                                     szInputBuffer, INPUTBUFFERSIZE,
  697.                                      nNumberOfBytesRead ) then
  698.                 begin
  699.                     PostHangupCall;
  700.                     goto EndReadThread;
  701.                 end;
  702.                 {break;}
  703.             end;
  704.  
  705.             WAIT_FAILED:       // Wait failed.  Shouldn't happen.
  706.             begin
  707.                 LogDebugLastError( GetLastError, 'Read WAIT_FAILED: ' );
  708.                 PostHangupCall;
  709.                 goto EndReadThread;
  710.             end;
  711.  
  712.             else    // This case should never occur.
  713.             begin
  714.                 LogDebugInfo( PChar('Unexpected Wait return value '+
  715.                             IntToStr(dwHandleSignaled)) );
  716.                 PostHangupCall;
  717.                 goto EndReadThread;
  718.             end;
  719.         end; {case dwHandleSignaled}
  720.     end; {while True}
  721.  
  722.     // Time to clean up Read Thread.
  723.  EndReadThread:
  724.  
  725.     LogDebugInfo( 'Read thread shutting down' );
  726.     PurgeComm( hCommFile, PURGE_RXABORT + PURGE_RXCLEAR );
  727.     CloseHandle( overlappedRead.hEvent );
  728.     CloseHandle( overlappedCommEvent.hEvent );
  729. end; {TReadThread.Execute}
  730.  
  731. //
  732. //  FUNCTION: SetupReadEvent(LPOVERLAPPED, LPSTR, DWORD, LPDWORD)
  733. //
  734. //  PURPOSE: Sets up an overlapped ReadFile
  735. //
  736. //  PARAMETERS:
  737. //    lpOverlappedRead      - address of overlapped structure to use.
  738. //    lpszInputBuffer       - Buffer to place incoming bytes.
  739. //    dwSizeofBuffer        - size of lpszInputBuffer.
  740. //    lpnNumberOfBytesRead  - address of DWORD to place the number of read bytes.
  741. //
  742. //  RETURN VALUE:
  743. //    TRUE if able to successfully setup the ReadFile.  FALSE if there
  744. //    was a failure setting up or if the CloseEvent object was signaled.
  745. //
  746. //  COMMENTS:
  747. //
  748. //    This function is a helper function for the Read Thread.  This
  749. //    function sets up the overlapped ReadFile so that it can later
  750. //    be waited on (or more appropriatly, so the event in the overlapped
  751. //    structure can be waited upon).  If there is data waiting, it is
  752. //    handled and the next ReadFile is initiated.
  753. //    Another possible reason for returning FALSE is if the comm port
  754. //    is closed by the service provider.
  755. //
  756. //
  757. //
  758. function TReadThread.SetupReadEvent( lpOverlappedRead: POverlapped;
  759.      lpszInputBuffer: LPSTR; dwSizeofBuffer: DWORD;
  760.      var lpnNumberOfBytesRead: DWORD ): Boolean;
  761. var
  762.      dwLastError: DWORD;
  763. label
  764.     StartSetupReadEvent;
  765. begin
  766.  
  767. StartSetupReadEvent:
  768.  
  769.     Result := False;
  770.     // Make sure the CloseEvent hasn't been signaled yet.
  771.     // Check is needed because this function is potentially recursive.
  772.     if WAIT_TIMEOUT <> WaitForSingleObject(hCloseEvent,0) then
  773.          Exit;
  774.  
  775.     // Start the overlapped ReadFile.
  776.     if ReadFile( hCommFile,
  777.               lpszInputBuffer^, dwSizeofBuffer,
  778.               lpnNumberOfBytesRead, lpOverlappedRead ) then
  779.     begin
  780.          // This would only happen if there was data waiting to be read.
  781.  
  782.         LogDebugInfo( 'Data waiting for ReadFile: ');
  783.  
  784.          // Handle the data.
  785.         if not HandleReadData( lpszInputBuffer, lpnNumberOfBytesRead ) then
  786.             Exit;
  787.  
  788.          // Start waiting for more data.
  789.         goto StartSetupReadEvent;
  790.     end;
  791.  
  792.     // ReadFile failed.  Expected because of overlapped I/O.
  793.     dwLastError := GetLastError;
  794.  
  795.  
  796.     // LastError was ERROR_IO_PENDING, as expected.
  797.     if dwLastError = ERROR_IO_PENDING then
  798.     begin
  799.          LogDebugInfo( 'Waiting for data from comm connection.' );
  800.          Result := True;
  801.          Exit;
  802.     end;
  803.  
  804.     // Its possible for this error to occur if the
  805.     // service provider has closed the port.  Time to end.
  806.     if dwLastError = ERROR_INVALID_HANDLE then
  807.     begin
  808.          LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  809.               'Likely that the Service Provider has closed the port.' );
  810.          Exit;
  811.     end;
  812.  
  813.     // Unexpected error. No idea what could cause this to happen.
  814.     LogDebugLastError( dwLastError, 'Unexpected ReadFile error: ' );
  815.  
  816.     PostHangupCall;
  817. end; {TReadThread.SetupReadEvent}
  818.  
  819. //
  820. //  FUNCTION: HandleReadData(LPCSTR, DWORD)
  821. //
  822. //  PURPOSE: Deals with data after its been read from the comm file.
  823. //
  824. //  PARAMETERS:
  825. //    lpszInputBuffer  - Buffer to place incoming bytes.
  826. //    dwSizeofBuffer   - size of lpszInputBuffer.
  827. //
  828. //  RETURN VALUE:
  829. //    TRUE if able to successfully handle the data.
  830. //    FALSE if unable to allocate memory or handle the data.
  831. //
  832. //  COMMENTS:
  833. //
  834. //    This function is yet another helper function for the Read Thread.
  835. //    It LocalAlloc()s a buffer, copies the new data to this buffer and
  836. //    calls PostWriteToDisplayCtl to let the EditCtls module deal with
  837. //    the data.  Its assumed that PostWriteToDisplayCtl posts the message
  838. //    rather than dealing with it right away so that the Read Thread
  839. //    is free to get right back to waiting for data.  Its also assumed
  840. //    that the EditCtls module is responsible for LocalFree()ing the
  841. //    pointer that is passed on.
  842. //
  843. //
  844. function TReadThread.HandleReadData( lpszInputBuffer: LPCSTR; dwSizeofBuffer: DWORD ): Boolean;
  845. var
  846.     lpszPostedBytes: LPSTR;
  847.     tempstr:                string;
  848. begin
  849.     Result := False;
  850.      // If we got data and didn't just time out empty...
  851.     if dwSizeofBuffer <> 0 then
  852.     begin
  853.         tempstr := lpszInputBuffer;
  854.  
  855.           // Do something with the bytes read.
  856.         LogDebugInfo( 'Got something from Comm port!!!' );
  857.  
  858.         lpszPostedBytes := PChar( LocalAlloc( LPTR, dwSizeofBuffer+1 ) );
  859.  
  860.         if lpszPostedBytes = nil{NULL} then
  861.         begin
  862.             LogDebugLastError( GetLastError, 'LocalAlloc: ' );
  863.             Exit;
  864.         end;
  865.  
  866.         Move( lpszInputBuffer^, lpszPostedBytes^, dwSizeofBuffer );
  867.         lpszPostedBytes[dwSizeofBuffer] := #0;
  868.  
  869.         Result := ReceiveData( lpszPostedBytes, dwSizeofBuffer );
  870.     end;
  871. end; {TReadThread.HandleReadData}
  872.  
  873. //
  874. //  FUNCTION: HandleReadEvent(LPOVERLAPPED, LPSTR, DWORD, LPDWORD)
  875. //
  876. //  PURPOSE: Retrieves and handles data when there is data ready.
  877. //
  878. //  PARAMETERS:
  879. //    lpOverlappedRead      - address of overlapped structure to use.
  880. //    lpszInputBuffer       - Buffer to place incoming bytes.
  881. //    dwSizeofBuffer        - size of lpszInputBuffer.
  882. //    lpnNumberOfBytesRead  - address of DWORD to place the number of read bytes.
  883. //
  884. //  RETURN VALUE:
  885. //    TRUE if able to successfully retrieve and handle the available data.
  886. //    FALSE if unable to retrieve or handle the data.
  887. //
  888. //  COMMENTS:
  889. //
  890. //    This function is another helper function for the Read Thread.  This
  891. //    is the function that is called when there is data available after
  892. //    an overlapped ReadFile has been setup.  It retrieves the data and
  893. //    handles it.
  894. //
  895. //
  896. function TReadThread.HandleReadEvent( lpOverlappedRead: POverlapped;
  897.      lpszInputBuffer: LPSTR; dwSizeofBuffer: DWORD;
  898.      var lpnNumberOfBytesRead: DWORD ): Boolean;
  899. var
  900.     dwLastError: DWORD;
  901. begin
  902.     Result := False;
  903.     if GetOverlappedResult( hCommFile,
  904.             lpOverlappedRead^, lpnNumberOfBytesRead, False ) then
  905.     begin
  906.         Result := HandleReadData( lpszInputBuffer, lpnNumberOfBytesRead );
  907.         Exit;
  908.     end;
  909.  
  910.     // Error in GetOverlappedResult; handle it.
  911.  
  912.     dwLastError := GetLastError;
  913.  
  914.     // Its possible for this error to occur if the
  915.     // service provider has closed the port.  Time to end.
  916.     if dwLastError = ERROR_INVALID_HANDLE then
  917.     begin
  918.         LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  919.                 'Likely that the Service Provider has closed the port.' );
  920.         Exit;
  921.     end;
  922.  
  923.     LogDebugLastError( dwLastError,
  924.           'Unexpected GetOverlappedResult Read Error: ' );
  925.  
  926.     PostHangupCall;
  927. end; {TReadThread.HandleReadEvent}
  928.  
  929. //
  930. //  FUNCTION: SetupCommEvent(LPOVERLAPPED, LPDWORD)
  931. //
  932. //  PURPOSE: Sets up the overlapped WaitCommEvent call.
  933. //
  934. //  PARAMETERS:
  935. //    lpOverlappedCommEvent - Pointer to the overlapped structure to use.
  936. //    lpfdwEvtMask          - Pointer to DWORD to received Event data.
  937. //
  938. //  RETURN VALUE:
  939. //    TRUE if able to successfully setup the WaitCommEvent.
  940. //    FALSE if unable to setup WaitCommEvent, unable to handle
  941. //    an existing outstanding event or if the CloseEvent has been signaled.
  942. //
  943. //  COMMENTS:
  944. //
  945. //    This function is a helper function for the Read Thread that sets up
  946. //    the WaitCommEvent so we can deal with comm events (like Comm errors)
  947. //    if they occur.
  948. //
  949. //
  950. function TReadThread.SetupCommEvent( lpOverlappedCommEvent: POverlapped;
  951.      var lpfdwEvtMask: DWORD ): Boolean;
  952. var
  953.     dwLastError: DWORD;
  954. label
  955.     StartSetupCommEvent;
  956. begin
  957.  
  958.     Result := False;
  959. StartSetupCommEvent:
  960.  
  961.      // Make sure the CloseEvent hasn't been signaled yet.
  962.      // Check is needed because this function is potentially recursive.
  963.     if WAIT_TIMEOUT <> WaitForSingleObject( hCloseEvent,0 ) then
  964.         Exit;
  965.  
  966.     // Start waiting for Comm Errors.
  967.     if WaitCommEvent( hCommFile, lpfdwEvtMask, lpOverlappedCommEvent ) then
  968.     begin
  969.         // This could happen if there was an error waiting on the
  970.         // comm port.  Lets try and handle it.
  971.  
  972.         LogDebugInfo( 'Event (Error) waiting before WaitCommEvent.' );
  973.  
  974.         if not HandleCommEvent( nil, lpfdwEvtMask, False ) then
  975.         {??? GetOverlappedResult does not handle "NIL" as defined by Borland}
  976.             Exit;
  977.  
  978.         // What could cause infinite recursion at this point?
  979.         goto StartSetupCommEvent;
  980.     end;
  981.  
  982.     // We expect ERROR_IO_PENDING returned from WaitCommEvent
  983.     // because we are waiting with an overlapped structure.
  984.  
  985.     dwLastError := GetLastError;
  986.  
  987.     // LastError was ERROR_IO_PENDING, as expected.
  988.     if dwLastError = ERROR_IO_PENDING then
  989.     begin
  990.         LogDebugInfo( 'Waiting for a CommEvent (Error) to occur.' );
  991.         Result := True;
  992.         Exit
  993.     end;
  994.  
  995.     // Its possible for this error to occur if the
  996.     // service provider has closed the port.  Time to end.
  997.     if dwLastError = ERROR_INVALID_HANDLE then
  998.     begin
  999.         LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  1000.                 'Likely that the Service Provider has closed the port.' );
  1001.         Exit;
  1002.     end;
  1003.  
  1004.     // Unexpected error. No idea what could cause this to happen.
  1005.     LogDebugLastError( dwLastError, 'Unexpected WaitCommEvent error: ' );
  1006. end; {TReadThread.SetupCommEvent}
  1007.  
  1008. //
  1009. //  FUNCTION: HandleCommEvent(LPOVERLAPPED, LPDWORD, BOOL)
  1010. //
  1011. //  PURPOSE: Handle an outstanding Comm Event.
  1012. //
  1013. //  PARAMETERS:
  1014. //    lpOverlappedCommEvent - Pointer to the overlapped structure to use.
  1015. //    lpfdwEvtMask          - Pointer to DWORD to received Event data.
  1016. //     fRetrieveEvent       - Flag to signal if the event needs to be
  1017. //                            retrieved, or has already been retrieved.
  1018. //
  1019. //  RETURN VALUE:
  1020. //    TRUE if able to handle a Comm Event.
  1021. //    FALSE if unable to setup WaitCommEvent, unable to handle
  1022. //    an existing outstanding event or if the CloseEvent has been signaled.
  1023. //
  1024. //  COMMENTS:
  1025. //
  1026. //    This function is a helper function for the Read Thread that (if
  1027. //    fRetrieveEvent == TRUE) retrieves an outstanding CommEvent and
  1028. //    deals with it.  The only event that should occur is an EV_ERR event,
  1029. //    signalling that there has been an error on the comm port.
  1030. //
  1031. //    Normally, comm errors would not be put into the normal data stream
  1032. //    as this sample is demonstrating.  Putting it in a status bar would
  1033. //    be more appropriate for a real application.
  1034. //
  1035. //
  1036. function TReadThread.HandleCommEvent( lpOverlappedCommEvent: POverlapped;
  1037.      var lpfdwEvtMask: DWORD; fRetrieveEvent: Boolean ): Boolean;
  1038. var
  1039.     dwDummy:            DWORD;
  1040.     lpszOutput:        LPSTR;
  1041.     szError:            array[0..127] of Char;
  1042.     dwErrors,
  1043.     nOutput,
  1044.     dwLastError:    DWORD;
  1045. begin
  1046.     Result := False;
  1047.  
  1048.     szError[0] := #0;
  1049.  
  1050.     lpszOutput := PChar(LocalAlloc( LPTR, 256 ));
  1051.     if lpszOutput = nil{NULL} then
  1052.     begin
  1053.         LogDebugLastError( GetLastError, 'LocalAlloc: ' );
  1054.         Exit;
  1055.     end;
  1056.  
  1057.     // If this fails, it could be because the file was closed (and I/O is
  1058.     // finished) or because the overlapped I/O is still in progress.  In
  1059.     // either case (or any others) its a bug and return FALSE.
  1060.     if fRetrieveEvent then
  1061.         if not GetOverlappedResult( hCommFile,
  1062.                      lpOverlappedCommEvent^, dwDummy, False ) then
  1063.         begin
  1064.             dwLastError := GetLastError;
  1065.  
  1066.             // Its possible for this error to occur if the
  1067.             // service provider has closed the port.  Time to end.
  1068.             if dwLastError = ERROR_INVALID_HANDLE then
  1069.             begin
  1070.                 LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  1071.                           'Likely that the Service Provider has closed the port.' );
  1072.                 Exit;
  1073.             end;
  1074.  
  1075.             LogDebugLastError( dwLastError,
  1076.                      'Unexpected GetOverlappedResult for WaitCommEvent: ' );
  1077.             Exit;
  1078.         end;
  1079.  
  1080.     // Was the event an error?
  1081.     if (lpfdwEvtMask and EV_ERR) <> 0 then
  1082.     begin
  1083.         // Which error was it?
  1084.         if not ClearCommError( hCommFile, dwErrors, nil ) then
  1085.         begin
  1086.             dwLastError := GetLastError;
  1087.  
  1088.             // Its possible for this error to occur if the
  1089.             // service provider has closed the port.  Time to end.
  1090.             if dwLastError = ERROR_INVALID_HANDLE then
  1091.             begin
  1092.                 LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  1093.                       'Likely that the Service Provider has closed the port.' );
  1094.                 Exit;
  1095.             end;
  1096.  
  1097.             LogDebugLastError( GetLastError,'ClearCommError: ' );
  1098.             Exit;
  1099.         end;
  1100.  
  1101.         // Its possible that multiple errors occured and were handled
  1102.         // in the last ClearCommError.  Because all errors were signaled
  1103.         // individually, but cleared all at once, pending comm events
  1104.         // can yield EV_ERR while dwErrors equals 0.  Ignore this event.
  1105.         if dwErrors = 0 then
  1106.             strcat( szError, 'NULL Error' );
  1107.  
  1108.         if (dwErrors and CE_FRAME) <> 0 then
  1109.         begin
  1110.             if szError[0] <> #0 then
  1111.                 strcat( szError, ' and ' );
  1112.  
  1113.             strcat( szError,'CE_FRAME' );
  1114.         end;
  1115.  
  1116.         if (dwErrors and CE_OVERRUN) <> 0 then
  1117.         begin
  1118.             if szError[0] <> #0 then
  1119.                 strcat(szError, ' and ' );
  1120.  
  1121.             strcat( szError, 'CE_OVERRUN' );
  1122.         end;
  1123.  
  1124.         if (dwErrors and CE_RXPARITY) <> 0 then
  1125.         begin
  1126.             if szError[0] <> #0 then
  1127.                 strcat( szError, ' and ' );
  1128.  
  1129.             strcat( szError, 'CE_RXPARITY' );
  1130.         end;
  1131.  
  1132.         if (dwErrors and not (CE_FRAME + CE_OVERRUN + CE_RXPARITY)) <> 0 then
  1133.         begin
  1134.             if szError[0] <> #0 then
  1135.                 strcat( szError, ' and ' );
  1136.  
  1137.             strcat( szError, 'EV_ERR Unknown EvtMask' );
  1138.         end;
  1139.  
  1140.         nOutput := wsprintf(lpszOutput,
  1141.                 PChar('Comm Event: '+szError+', EvtMask = '+IntToStr(dwErrors)) );
  1142.  
  1143.         ReceiveData( lpszOutput, nOutput );
  1144.         Result := True;
  1145.         Exit
  1146.     end;
  1147.  
  1148.     // Should not have gotten here.  Only interested in ERR conditions.
  1149.  
  1150.     LogDebugInfo( PChar('Unexpected comm event '+IntToStr(lpfdwEvtMask)) );
  1151. end; {TReadThread.HandleCommEvent}
  1152.  
  1153. function TReadThread.ReceiveData( lpNewString: LPSTR; dwSizeofNewString: DWORD ): BOOL;
  1154. begin
  1155.     Result := PostMessage( hComm32Window, PWM_GOTCOMMDATA,
  1156.           WPARAM(dwSizeofNewString), LPARAM(lpNewString) );
  1157. end;
  1158.  
  1159. procedure TReadThread.PostHangupCall;
  1160. begin
  1161.     PostMessage( hComm32Window, PWM_REQUESTHANGUP, 0, 0 );
  1162. end;
  1163.  
  1164. (******************************************************************************)
  1165. //                                            WRITE THREAD
  1166. (******************************************************************************)
  1167.  
  1168. //
  1169. //  PROCEDURE: TWriteThread.Execute
  1170. //
  1171. //  PURPOSE: The starting point for the Write thread.
  1172. //
  1173. //  PARAMETERS:
  1174. //    lpvParam - unused.
  1175. //
  1176. //  RETURN VALUE:
  1177. //    DWORD - unused.
  1178. //
  1179. //  COMMENTS:
  1180. //
  1181. //    The Write thread uses a PeekMessage loop to wait for a string to write,
  1182. //    and when it gets one, it writes it to the Comm port.  If the CloseEvent
  1183. //    object is signaled, then it exits.  The use of messages to tell the
  1184. //    Write thread what to write provides a natural desynchronization between
  1185. //    the UI and the Write thread.
  1186. //
  1187. //
  1188. procedure TWriteThread.Execute;
  1189. var
  1190.      msg:    TMsg;
  1191.      dwHandleSignaled:    DWORD;
  1192.      overlappedWrite:        TOverLapped;
  1193. label
  1194.     EndWriteThread;
  1195. begin
  1196.  
  1197.      // Needed for overlapped I/O.
  1198.      FillChar( overlappedWrite, SizeOf(overlappedWrite), 0 );  {0, 0, 0, 0, NULL}
  1199.  
  1200.      overlappedWrite.hEvent := CreateEvent( nil, True, True, nil );
  1201.      if overlappedWrite.hEvent = 0 then
  1202.      begin
  1203.           LogDebugLastError( GetLastError, 'Unable to CreateEvent: ' );
  1204.           PostHangupCall;
  1205.           goto EndWriteThread;
  1206.      end;
  1207.  
  1208.      // This is the main loop.  Loop until we break out.
  1209.      while True do
  1210.      begin
  1211.           if not PeekMessage( msg, 0, 0, 0, PM_REMOVE ) then
  1212.           begin
  1213.                 // If there are no messages pending, wait for a message or
  1214.                 // the CloseEvent.
  1215.                 dwHandleSignaled :=
  1216.                      MsgWaitForMultipleObjects(1, hCloseEvent, False,
  1217.                           INFINITE, QS_ALLINPUT);
  1218.  
  1219.                 case dwHandleSignaled of
  1220.                      WAIT_OBJECT_0:     // CloseEvent signaled!
  1221.                      begin
  1222.                           // Time to exit.
  1223.                           goto EndWriteThread;
  1224.                      end;
  1225.  
  1226.                      WAIT_OBJECT_0 + 1: // New message was received.
  1227.                      begin
  1228.                           // Get the message that woke us up by looping again.
  1229.                           continue;
  1230.                      end;
  1231.  
  1232.                      WAIT_FAILED:       // Wait failed.  Shouldn't happen.
  1233.                      begin
  1234.                           LogDebugLastError( GetLastError, 'Write WAIT_FAILED: ' );
  1235.                           PostHangupCall;
  1236.                           goto EndWriteThread;
  1237.                      end;
  1238.  
  1239.                      else                // This case should never occur.
  1240.                      begin
  1241.                           LogDebugInfo( PChar('Unexpected Wait return value '
  1242.                                                         +IntToStr(dwHandleSignaled)) );
  1243.                           PostHangupCall;
  1244.                           goto EndWriteThread;
  1245.                      end;
  1246.                 end;
  1247.           end;
  1248.  
  1249.           // Make sure the CloseEvent isn't signaled while retrieving messages.
  1250.           if WAIT_TIMEOUT <> WaitForSingleObject(hCloseEvent,0) then
  1251.                 goto EndWriteThread;
  1252.  
  1253.           // Process the message.
  1254.  
  1255.           // This could happen if a dialog is created on this thread.
  1256.           // This doesn't occur in this sample, but might if modified.
  1257.           if msg.hwnd <> 0{NULL} then
  1258.           begin
  1259.                 TranslateMessage(msg);
  1260.                 DispatchMessage(msg);
  1261.  
  1262.                 continue;
  1263.           end;
  1264.  
  1265.           // Handle the message.
  1266.           case msg.message of
  1267.                 PWM_COMMWRITE:  // New string to write to Comm port.
  1268.                 begin
  1269.                      LogDebugInfo( 'Writing to comm port' );
  1270.  
  1271.                      // Write the string to the comm port.  HandleWriteData
  1272.                      // does not return until the whole string has been written,
  1273.                      // an error occurs or until the CloseEvent is signaled.
  1274.                      if not HandleWriteData( @overlappedWrite,
  1275.                                 PChar(msg.lParam), DWORD(msg.wParam) ) then
  1276.                      begin
  1277.                           // If it failed, either we got a signal to end or there
  1278.                           // really was a failure.
  1279.  
  1280.                           LocalFree( HLOCAL(msg.lParam) );
  1281.                           goto EndWriteThread;
  1282.                      end;
  1283.  
  1284.                      // Data was sent in a LocalAlloc()d buffer.  Must free it.
  1285.                      LocalFree( HLOCAL(msg.lParam) );
  1286.                 end;
  1287.  
  1288.                 // What other messages could the thread get?
  1289.                 else
  1290.                 begin
  1291.                      LogDebugInfo( PChar('Unexpected message posted to Write thread: '+
  1292.                           IntToStr(msg.message)) );
  1293.                      {break;}
  1294.                 end;
  1295.           end; {case}
  1296.      end; {main loop}
  1297.  
  1298.      // Thats the end.  Now clean up.
  1299.   EndWriteThread:
  1300.  
  1301.      LogDebugInfo( 'Write thread shutting down' );
  1302.  
  1303.      PurgeComm(hCommFile, PURGE_TXABORT + PURGE_TXCLEAR);
  1304.  
  1305.      CloseHandle(overlappedWrite.hEvent);
  1306. end; {TWriteThread.Execute}
  1307.  
  1308.  
  1309. //
  1310. //  FUNCTION: HandleWriteData(LPOVERLAPPED, LPCSTR, DWORD)
  1311. //
  1312. //  PURPOSE: Writes a given string to the comm file handle.
  1313. //
  1314. //  PARAMETERS:
  1315. //    lpOverlappedWrite      - Overlapped structure to use in WriteFile
  1316. //    pDataToWrite      - String to write.
  1317. //    dwNumberOfBytesToWrite - Length of String to write.
  1318. //
  1319. //  RETURN VALUE:
  1320. //    TRUE if all bytes were written.  False if there was a failure to
  1321. //    write the whole string.
  1322. //
  1323. //  COMMENTS:
  1324. //
  1325. //    This function is a helper function for the Write Thread.  It
  1326. //    is this call that actually writes a string to the comm file.
  1327. //    Note that this call blocks and waits for the Write to complete
  1328. //    or for the CloseEvent object to signal that the thread should end.
  1329. //    Another possible reason for returning FALSE is if the comm port
  1330. //    is closed by the service provider.
  1331. //
  1332. //
  1333. function TWriteThread.HandleWriteData( lpOverlappedWrite: POverlapped;
  1334.      pDataToWrite: PChar; dwNumberOfBytesToWrite: DWORD): Boolean;
  1335. var
  1336.     dwLastError,
  1337.  
  1338.     dwNumberOfBytesWritten,
  1339.     dwWhereToStartWriting,
  1340.  
  1341.     dwHandleSignaled:    DWORD;
  1342.     HandlesToWaitFor: array[0..1] of THandle;
  1343. begin
  1344.     dwNumberOfBytesWritten := 0;
  1345.     dwWhereToStartWriting := 0; // Start at the beginning.
  1346.  
  1347.     HandlesToWaitFor[0] := hCloseEvent;
  1348.     HandlesToWaitFor[1] := lpOverlappedWrite^.hEvent;
  1349.  
  1350.      // Keep looping until all characters have been written.
  1351.      repeat
  1352.           // Start the overlapped I/O.
  1353.           if not WriteFile(hCommFile,
  1354.                      pDataToWrite[ dwWhereToStartWriting ],
  1355.                      dwNumberOfBytesToWrite, dwNumberOfBytesWritten,
  1356.                      lpOverlappedWrite) then
  1357.           begin
  1358.                 // WriteFile failed.  Expected; lets handle it.
  1359.                 dwLastError := GetLastError;
  1360.  
  1361.                 // Its possible for this error to occur if the
  1362.                 // service provider has closed the port.  Time to end.
  1363.                 if (dwLastError = ERROR_INVALID_HANDLE) then
  1364.                 begin
  1365.                      LogDebugInfo( 'ERROR_INVALID_HANDLE, '+
  1366.                           'Likely that the Service Provider has closed the port.' );
  1367.                      Result := False;
  1368.                      Exit;
  1369.                 end;
  1370.  
  1371.                 // Unexpected error.  No idea what.
  1372.                 if dwLastError <> ERROR_IO_PENDING then
  1373.                 begin
  1374.                      LogDebugLastError( dwLastError, 'Error to writing to CommFile' );
  1375.  
  1376.                      LogDebugInfo( 'Closing TAPI' );
  1377.                      PostHangupCall;
  1378.                      Result := False;
  1379.                      Exit;
  1380.                 end;
  1381.  
  1382.                 // This is the expected ERROR_IO_PENDING case.
  1383.  
  1384.  
  1385.                 // Wait for either overlapped I/O completion,
  1386.                 // or for the CloseEvent to get signaled.
  1387.                 dwHandleSignaled :=
  1388.                      WaitForMultipleObjects(2, @HandlesToWaitFor,
  1389.                           False, INFINITE);
  1390.  
  1391.                 case dwHandleSignaled of
  1392.                      WAIT_OBJECT_0:     // CloseEvent signaled!
  1393.                      begin
  1394.                           // Time to exit.
  1395.                           Result := False;
  1396.                           Exit;
  1397.                      end;
  1398.  
  1399.                      WAIT_OBJECT_0 + 1: // Wait finished.
  1400.                      begin
  1401.                           // Time to get the results of the WriteFile
  1402.                      end;
  1403.  
  1404.                      WAIT_FAILED: // Wait failed.  Shouldn't happen.
  1405.                      begin
  1406.                           LogDebugLastError( GetLastError, 'Write WAIT_FAILED: ' );
  1407.                           PostHangupCall;
  1408.                           Result := False;
  1409.                           Exit
  1410.                      end;
  1411.  
  1412.                      else // This case should never occur.
  1413.                      begin
  1414.                           LogDebugInfo( PChar('Unexpected Wait return value '+
  1415.                                                     IntToStr(dwHandleSignaled)) );
  1416.                           PostHangupCall;
  1417.                           Result := False;
  1418.                           Exit
  1419.                      end;
  1420.                 end; {case}
  1421.  
  1422.                 if not GetOverlappedResult(hCommFile,
  1423.                             lpOverlappedWrite^,
  1424.                             dwNumberOfBytesWritten, TRUE) then
  1425.                 begin
  1426.                      dwLastError := GetLastError();
  1427.  
  1428.                      // Its possible for this error to occur if the
  1429.                      // service provider has closed the port.
  1430.                      if dwLastError = ERROR_INVALID_HANDLE then
  1431.                      begin
  1432.                           LogDebugInfo('ERROR_INVALID_HANDLE, '+
  1433.                                 'Likely that the Service Provider has closed the port.');
  1434.                           Result := False;
  1435.                           Exit;
  1436.                      end;
  1437.  
  1438.                      // No idea what could cause another error.
  1439.                      LogDebugLastError( dwLastError, 'Error writing to CommFile while waiting');
  1440.                      LogDebugInfo('Closing TAPI');
  1441.                      PostHangupCall;
  1442.                      Result := False;
  1443.                      Exit;
  1444.                 end;
  1445.           end; {WriteFile failure}
  1446.  
  1447.           // Some data was written.  Make sure it all got written.
  1448.  
  1449.           Dec( dwNumberOfBytesToWrite, dwNumberOfBytesWritten );
  1450.           Inc( dwWhereToStartWriting, dwNumberOfBytesWritten );
  1451.      until (dwNumberOfBytesToWrite <= 0);  // Write the whole thing!
  1452.  
  1453.      // Wrote the whole string.
  1454.      Result := True;
  1455. end; {TWriteThread.HandleWriteData}
  1456.  
  1457. function TWriteThread.WriteComm( pDataToWrite: LPCSTR; dwSizeofDataToWrite: DWORD ): Boolean;
  1458. begin
  1459.     Result := PostThreadMessage( ThreadID, PWM_COMMWRITE,
  1460.                      WParam(dwSizeofDataToWrite), LParam(pDataToWrite) );
  1461. end;
  1462.  
  1463. procedure TWriteThread.PostHangupCall;
  1464. begin
  1465.     PostMessage( hComm32Window, PWM_REQUESTHANGUP, 0, 0 );
  1466. end;
  1467.  
  1468. (******************************************************************************)
  1469. //                                            DEBUG ROUTINES
  1470. (******************************************************************************)
  1471.  
  1472. //
  1473. //  FUNCTION: LogDebugLastError(..)
  1474. //
  1475. //  PURPOSE: Pretty print a line error to the debugging output.
  1476. //
  1477. //  PARAMETERS:
  1478. //    dwLastError - Actual error code to decipher.
  1479. //    pszPrefix   - String to prepend to the printed message.
  1480. //
  1481. //  RETURN VALUE:
  1482. //    none
  1483. //
  1484. //  COMMENTS:
  1485. //
  1486. //    Note that there is an internal string length limit of
  1487. //    MAXOUTPUTSTRINGLENGTH.  If this length is exceeded,
  1488. //    the behavior will be the same as wsprintf, although
  1489. //    it will be undetectable.  *KEEP szPrefix SHORT!*
  1490. //
  1491. //
  1492. procedure LogDebugLastError( dwLastError: DWORD; szPrefix: LPSTR );
  1493. var
  1494.     szLastError: LPSTR;
  1495.     szOutputLastError: array[0..MAXOUTPUTSTRINGLENGTH-1] of Char;
  1496. begin
  1497.     if szPrefix = nil then
  1498.         szPrefix := '';
  1499.  
  1500.     // Pretty print the error.
  1501.     szLastError := FormatLastError(dwLastError, nil, 0);
  1502.  
  1503.     // The only reason FormatLastError should fail is "Out of memory".
  1504.     if szLastError = nil then
  1505.     begin
  1506.         wsprintf( szOutputLastError, PChar(szPrefix+'Out of memory') );
  1507.  
  1508.         LogDebugInfo( szOutputLastError );
  1509.  
  1510.         Exit;
  1511.     end;
  1512.  
  1513.     wsprintf( szOutputLastError,
  1514.               PChar(szPrefix+'GetLastError returned: "'+szLastError+'"') );
  1515.  
  1516.     // Pointer returned from FormatLineError *must* be freed!
  1517.     LocalFree( HLOCAL(szLastError) );
  1518.  
  1519.     // Print it!
  1520.     LogDebugInfo( szOutputLastError );
  1521. end; {LogDebugLastError}
  1522.  
  1523. procedure LogDebugInfo( outstr: PChar );
  1524. begin
  1525.     if CommsLogName <> '' then
  1526.         Writeln( CommsLogFile, outstr );
  1527. end; {LogDebugInfo}
  1528.  
  1529. procedure Register;
  1530. begin
  1531.   RegisterComponents('Stamina', [TComm32]);
  1532. end;
  1533.  
  1534. end.
  1535.